package cn.fintecher.pangolin.service.dataimp.service;

import cn.fintecher.pangolin.common.enums.*;
import cn.fintecher.pangolin.common.model.TaskBoxModel;
import cn.fintecher.pangolin.common.model.UserModel;
import cn.fintecher.pangolin.common.utils.ZWDateUtil;
import cn.fintecher.pangolin.entity.elastic.BaseCase;
import cn.fintecher.pangolin.entity.elastic.Strategy;
import cn.fintecher.pangolin.entity.mysql.CaseInfo;
import cn.fintecher.pangolin.entity.mysql.QCaseInfo;
import cn.fintecher.pangolin.service.dataimp.elastic.BaseCaseElastic;
import cn.fintecher.pangolin.service.dataimp.elastic.StrategyElastic;
import cn.fintecher.pangolin.service.dataimp.model.StrategyExecuteModel;
import cn.fintecher.pangolin.service.dataimp.repository.CaseInfoRepository;
import cn.fintecher.pangolin.service.dataimp.task.CaseInfoStrategyTask;
import cn.fintecher.pangolin.service.dataimp.task.StrategyTask;
import cn.fintecher.pangolin.common.utils.RepositoryUtil;
import com.querydsl.core.BooleanBuilder;
import org.apache.commons.collections4.IterableUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * @Auther: xiaqun
 * @Description:
 * @Date: 16:37 2019/9/16
 */

@Service("/strategyDistributeService")
@Transactional(rollbackFor = Exception.class)
public class StrategyDistributeService {
    private static final Logger log = LoggerFactory.getLogger(StrategyDistributeService.class);

    @Autowired
    private BaseService baseService;

    @Autowired
    private StrategyElastic strategyElastic;

    @Autowired
    private BaseCaseElastic baseCaseElastic;

    @Autowired
    private StrategyTask strategyTask;

    @Autowired
    private CaseInfoRepository caseInfoRepository;

    @Autowired
    private CaseInfoStrategyTask caseInfoStrategyTask;

    /**
     * 手动执行策略分案(打标)
     */
    @Async
    public void manualExecuteStrategy(StrategyType type, String token) {
        UserModel userModel = baseService.getUserByToken(token);
        BoolQueryBuilder builder1 = new BoolQueryBuilder();
        builder1.must(QueryBuilders.matchPhraseQuery("strategyType.keyword", type.name()));
        Iterable<Strategy> iterable = strategyElastic.search(builder1);
        List<Strategy> list = IterableUtils.toList(iterable);
        RulesEngine rulesEngine = new DefaultRulesEngine();
        TaskBoxModel model = baseService.getTaskBox(TaskBoxType.EXPORT, "策略分案", userModel);
        String name = "";
        switch (type) {
            case WAIT_DISTRIBUTE:
                name = "待分配案件池策略";
                break;
            case DEPARTMENT:
                name = "部门待分配案件池策略";
                break;
            case COLLECTING:
                name = "在催案件池策略";
                break;
            case PUBLIC:
                name = "公共案件池策略";
                break;
            case STOPPING:
                name = "停催案件池策略";
                break;
            case RETURN:
                name = "退案案件池策略";
                break;
            case DELETE:
                name = "删除案件池策略";
                break;
            case CIRCULATION:
                name = "流转案件池策略";
        }
        baseService.sendTaskBoxMessage(model, TaskBoxType.STRATEGY, TaskBoxStatus.UN_FINISH, null, null, userModel, ZWDateUtil.getNowDateTime(), null, name + "正在执行中");
        List<CompletableFuture<StrategyExecuteModel>> futures = new ArrayList<>();
        List<StrategyExecuteModel> models = new ArrayList<>();
        if (type.equals(StrategyType.WAIT_DISTRIBUTE)) {
            log.info("<--------------------------案件打标开始-------------------------->");
            if (!list.isEmpty()) { //策略不为空
                list.sort(Comparator.comparing(Strategy::getPriority));
                Rules rules = createRules(list);
                BoolQueryBuilder builder = new BoolQueryBuilder();
                builder.must(QueryBuilders.matchPhraseQuery("isDistribute.keyword", BaseSelect.NO.name()));
                builder.mustNot(QueryBuilders.existsQuery("strategyId"));
                getBaseCase(builder, rulesEngine, rules);
                log.info("<--------------------------案件打标结束-------------------------->");
                log.info("<--------------------------开始策略分案-------------------------->");
                for (Strategy strategy : list) {
                    log.info("执行{}策略开始......", strategy.getName());
                    CompletableFuture<StrategyExecuteModel> future = strategyTask.strategyDistribute(strategy, userModel);
                    futures.add(future);
                }
                try {
                    for (CompletableFuture<StrategyExecuteModel> future : futures) {
                        models.add(future.get());
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    baseService.sendTaskBoxMessage(model, TaskBoxType.STRATEGY, TaskBoxStatus.FAILURE, null, null, userModel, null, ZWDateUtil.getNowDateTime(), "策略分案多线程处理异常");
                }
            } else {
                log.info("没有待分配案件池策略");
                baseService.sendTaskBoxMessage(model, TaskBoxType.STRATEGY, TaskBoxStatus.FAILURE, null, null, userModel, null, ZWDateUtil.getNowDateTime(), "没有对应的案件池策略");
            }
        } else {
            if (!list.isEmpty()) { //策略不为空
                QCaseInfo qCaseInfo = QCaseInfo.caseInfo;
                BooleanBuilder builder = new BooleanBuilder();
                if (type.equals(StrategyType.DEPARTMENT)) {
                    log.info("<--------------------------开始过滤部门待分配案件-------------------------->");
                    builder.and(qCaseInfo.department.isNotNull()).and(qCaseInfo.currentCollector.isNull())
                            .and(qCaseInfo.caseDataStatus.notIn(CaseDataStatus.PAUSE, CaseDataStatus.CIRCULATION).or(qCaseInfo.caseDataStatus.isNull()))
                            .and(qCaseInfo.publicCaseFlag.eq(PublicCaseFlag.NO_PUBLIC_CASE).or(qCaseInfo.publicCaseFlag.isNull()));
                } else if (type.equals(StrategyType.COLLECTING)) {
                    log.info("<--------------------------开始过滤在催案件-------------------------->");
                    builder.and(qCaseInfo.department.isNull()).and(qCaseInfo.currentCollector.isNotNull())
                            .and(qCaseInfo.caseDataStatus.eq(CaseDataStatus.IN_POOL))
                            .and(qCaseInfo.publicCaseFlag.eq(PublicCaseFlag.NO_PUBLIC_CASE).or(qCaseInfo.publicCaseFlag.isNull()));
                } else if (type.equals(StrategyType.PUBLIC)) {
                    log.info("<--------------------------开始过滤公共案件-------------------------->");
                    builder.and(qCaseInfo.publicCaseFlag.eq(PublicCaseFlag.PUBLIC_CASE));
                } else if (type.equals(StrategyType.STOPPING)) {
                    log.info("<--------------------------开始过滤停催案件-------------------------->");
                    builder.and(qCaseInfo.caseDataStatus.eq(CaseDataStatus.PAUSE));
                } else if (type.equals(StrategyType.CIRCULATION)) {
                    log.info("<--------------------------开始过滤流转案件-------------------------->");
                    builder.and(qCaseInfo.caseDataStatus.eq(CaseDataStatus.CIRCULATION));
                }
                list.sort(Comparator.comparing(Strategy::getPriority));
                Rules rules = createDepartRules(list);
                getDepartmentCase(builder, rulesEngine, rules);
                for (Strategy strategy : list) {
                    Iterable<CaseInfo> caseInfoIterable = caseInfoRepository.findAll(QCaseInfo.caseInfo.strategyId.eq(strategy.getId()));
                    List<CaseInfo> caseInfoList = IterableUtils.toList(caseInfoIterable);
                    log.info("执行{}策略开始......", strategy.getName());
                    CompletableFuture<StrategyExecuteModel> future = caseInfoStrategyTask.strategyDistribute(strategy, caseInfoList, userModel);
                    futures.add(future);
                }
                try {
                    for (CompletableFuture<StrategyExecuteModel> future : futures) {
                        models.add(future.get());
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                    baseService.sendTaskBoxMessage(model, TaskBoxType.STRATEGY, TaskBoxStatus.FAILURE, null, null, userModel, null, ZWDateUtil.getNowDateTime(), "策略分案多线程处理异常");
                }
            } else {
                log.info("没有对应案件池策略");
                baseService.sendTaskBoxMessage(model, TaskBoxType.STRATEGY, TaskBoxStatus.FAILURE, null, null, userModel, null, ZWDateUtil.getNowDateTime(), "没有对应的案件池策略");
            }
        }
        StringBuilder builder2 = new StringBuilder();
        builder2.append(name).append("执行完成:");
        models.forEach(e -> builder2.append("【").append(e.getStrategy().getName()).append("】策略共分配【").append(e.getNumber()).append("】个案件 "));
        baseService.sendTaskBoxMessage(model, TaskBoxType.STRATEGY, TaskBoxStatus.FINISHED, null, null, userModel, null, ZWDateUtil.getNowDateTime(), builder2.toString());
    }

    /**
     * 分页查询待分配案件
     */
    private void getBaseCase(BoolQueryBuilder builder, RulesEngine engine, Rules rules) {
        Iterable<BaseCase> personalPage = baseCaseElastic.search(builder);
        List<BaseCase> personalList = IterableUtils.toList(personalPage);
        if (!personalList.isEmpty()) {
            for (BaseCase baseCase : personalList) {
                executeRule(baseCase, engine, rules);
            }
            RepositoryUtil.saveData(baseCaseElastic, personalList);
        }
    }

    /**
     * 查询部门待分配案件
     */
    private void getDepartmentCase(BooleanBuilder builder, RulesEngine engine, Rules rules) {
        Iterable<CaseInfo> caseInfoPage = caseInfoRepository.findAll(builder);
        List<CaseInfo> caseInfos = IterableUtils.toList(caseInfoPage);
        if (!caseInfos.isEmpty()) {
            for (CaseInfo caseInfo : caseInfos) {
                executeDepartmentRule(caseInfo, engine, rules);
            }
            RepositoryUtil.saveData(caseInfoRepository, caseInfos);
        }
    }

    /**
     * 待分配案件规则引擎
     */
    private void executeRule(BaseCase baseCase, RulesEngine engine, Rules rules) {
        Facts facts = new Facts();
        facts.put("baseCase", baseCase);
        engine.fire(rules, facts);
    }

    /**
     * 机构待分配案件规则引擎
     */
    private void executeDepartmentRule(CaseInfo caseInfo, RulesEngine rulesEngine, Rules rules) {
        Facts facts = new Facts();
        facts.put("caseInfo", caseInfo);
        rulesEngine.fire(rules, facts);
    }

    /**
     * 生成待分配rules
     */
    private Rules createRules(List<Strategy> list) {
        Rules rules = new Rules();
        for (Strategy strategy : list) {
            Rule rule = new MVELRule()
                    .priority(strategy.getPriority())
                    .name(strategy.getId())
                    .description("待分配案件规则引擎")
                    .when(strategy.getFormula())
                    .then("if(null == baseCase.getStrategyId()){baseCase.setStrategyId('" + strategy.getId() + "');}");
            rules.register(rule);
        }
        return rules;
    }

    /**
     * 生成部门分配rules
     */
    private Rules createDepartRules(List<Strategy> list) {
        Rules rules = new Rules();
        for (Strategy strategy : list) {
            Rule rule = new MVELRule()
                    .name(strategy.getId())
                    .description("机构待分配案件规则引擎")
                    .when(strategy.getFormula())
                    .then("if(null == caseInfo.getStrategyId()){caseInfo.setStrategyId('" + strategy.getId() + "');}");
            rules.register(rule);
        }
        return rules;
    }
}
